// Copyright 2006 Bret Taylor
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#include <config.h>
#include <getopt.h>
#include <stdlib.h>
#include <iostream>

#include "compiler.h"
#include "debug.h"
#include "localization.h"
#include "log.h"

using namespace indie;
using namespace std;

static void PrintHelp(ostream &out, const char* path);
static void PrintHint(ostream &out, const char* path);
static void PrintPackageVersion(ostream &out);

// Parse the command line options and start the compiler.
int main(int argc, char* argv[]) {
    // Options that can be changed on the command line
    bool warnings_as_errors = false;
    bool report_warnings = true;
    bool report_errors = true;
    enum { Emit, Parse, Check, PrintAst } action = Emit;

    // Parse command line options
    const struct option options[] = {
	{"warnings", no_argument, NULL, 'w'},
	{"quiet", no_argument, NULL, 'q'},
	{"very-quiet", no_argument, NULL, 'Q'},
	{"emit", no_argument, NULL, 'e'},
	{"check", no_argument, NULL, 'c'},
	{"parse", no_argument, NULL, 'p'},
	{"print-ast", no_argument, NULL, 'a'},
	{"debug", required_argument, NULL, 'D'},
	{"help", no_argument, NULL, 'h'},
	{"version", no_argument, NULL, 'V'},
	{NULL, 0, NULL, 0}
    };
    while (true) {
	int c = getopt_long(argc, argv, "wqQecpad:hV", options, NULL);
	if (c == -1)
            break;

	switch (c) {
	case 'w':
	    warnings_as_errors = true;
	    break;
	case 'q':
	    report_warnings = false;
	    break;
	case 'Q':
	    report_warnings = false;
	    report_errors = false;
	    break;
	case 'p':
	    action = Parse;
	    break;
	case 'c':
	    action = Check;
	    break;
	case 'a':
	    action = PrintAst;
	    break;
	case 'e':
	    action = Emit;
	    break;
	case 'd':
	    Debug::instance().EnableKey(optarg);
	    break;
	case 'h':
	    PrintHelp(cerr, argv[0]);
	    return EXIT_SUCCESS;
	case 'V':
	    PrintPackageVersion(cerr);
	    return EXIT_SUCCESS;
	default:
	    PrintHint(cerr, argv[0]);
	    return EXIT_FAILURE;
	}
    }

    // Construct our set of files to compile. We must have at least one
    // file, so show command line help if none is given.
    if (optind == argc) {
	cerr << _("Usage: ") << argv[0] << " [OPTIONS] FILE0 FILE1 ..."
	     << endl;
	PrintHint(cerr, argv[0]);
	return EXIT_FAILURE;
    }
    set<string> paths;
    for (int i = optind; i < argc; i++) {
	paths.insert(argv[i]);
    }

    // Now perform the requested action on the set of files
    Log log(report_warnings, report_errors);
    Compiler compiler(log);
    switch (action) {
    case Parse:
	{ auto_collection_ptr<set<AstFile*> > asts(compiler.Parse(paths)); }
	break;
    case Check:
	compiler.Check(paths);
	break;
    case PrintAst:
	compiler.PrintAst(paths, cout);
	break;
    default:
	compiler.Compile(paths, cout);
	break;
    }

    // Return the number of errors as our return code so that other
    // applications can derive value from our error codes.
    int num_errors = log.num_errors();
    if (warnings_as_errors) num_errors += log.num_warnings();
    return num_errors;
}


// Prints a brief description of the command line options to the given
// output stream, where path is the path to this application.
static void PrintHelp(ostream &out, const char* path) {
    out << _("Usage: ") << path << " [OPTIONS] FILE0 FILE1 ..." << endl;

    out << endl;
    out << _("Compiler options:") << endl;
    out << "  -w, --warnings    "
	<< _("treat warnings as errors") << endl;
    out << "  -q, --quiet       "
	<< _("suppress warning messages") << endl;
    out << "  -Q, --very-quiet  "
	<< _("suppress error messages") << endl;

    out << endl;
    out << _("Compiler actions:") << endl;
    out << "  -e, --emit        "
	<< _("complete all compiler stages (default)") << endl;
    out << "  -c, --check       "
	<< _("parse and check the files, but do not emit code") << endl;
    out << "  -p, --parse       "
	<< _("parse the files, but do not check or emit code") << endl;
    out << "  -a, --print-ast   "
	<< _("print the AST of all of the files") << endl;

    out << endl;
    out << _("Debugging options:") << endl;
    out << "  -D, --debug=KEY   "
	<< _("turns on debug messages for the given key") << endl;

    out << endl;
    out << _("Other options:") << endl;
    out << "  -h, --help        "
	<< _("prints this help information") << endl;
    out << "  -V, --version     "
	<< _("prints this package's version") << endl;

    out << endl;
    out << _("The Indie compiler, which translates Indie into ANSI C.\n"
	     "For more information, visit http://indie.sourceforge.net/.\n"
	     "You can contact the author, Bret Taylor, at btaylor@gmail.com.")
	<< endl;
}


// Prints a command-line reminder to the given output stream, where path is
// the path to this application.
static void PrintHint(ostream &out, const char* path) {
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), _("Try '%s %s' for more information"),
	     path, "--help");
    out << buffer << endl;
}


// Prints the package information (name and version) to the given output
// stream, followed by a carriage return.
static void PrintPackageVersion(ostream &out) {
    out << PACKAGE << ' ' << VERSION << endl;
}
